cobysher.dev

I Deployed a `coturn` Server to AWS

#coding #theWeb #learning #devOps #aws

Thanks to https://gabrieltanner.org/blog/turn-server/, last night I successfully deployed my first coturn server on an aws ec2 instance. Why you might ask? I am building a thing which needs a WebRTC Peer Connection! Everything in the guide worked as advertised, but I want to have this repeatable. While Terraform is no longer the open source sweetheart it was when I started learning it 6 months ago, I wanted to build on the little knowledge I have of it. Plus I’m using it for personal reasons so it’s still fine as far as licenses are concerned.

Scripting the setup

please ignore the broken syntax highlighting and random backticks. There’s a bug somewhere and I’m in the process of fixing it but the bug is breaking my whole blog so I’d rather this one post be a bit broken than the whole blog. Thanks!

Maybe there is a better way, but it seems straightforward to add an init template that runs when the instance starts. The template is a bash script that installs and configures the coturn server, as well as certbot as the article above suggests. One thing that will need to be done in the terraform is opening the proper ports. The default security group won’t have the proper config so make sure not to skip that part, otherwise you’ll see timeout errors on the testing site.

You can choose to skip user credentials on the turnserver depending on your use case. I’m going to set them up via the suggested CLI args in the “docs”, which as far as I can tell is limited to this very well commented .conf file.

This is meant for running as an init script on aws ec2. I am using ubuntu since I don’t want to deal withyum or building the coturn source.

Feel free to use the code below. No guarantees or warranties 😊.

The Script

TLDR: install coturn and certbot, configure them, and flip the ON switch!


#!/bin/sh

# shellcheck disable=SC2154

  

echo 'Initializing turn server...'

  

set -e

  

#ubuntu needs root

sudo -i

  

echo 'Installing stuff...'

#install coturn and cerbot

apt-get update -y

apt-get install coturn certbot -y

  

echo 'Setting config...'

# enable the server

cat >> /etc/default/coturn << EOF

TURNSERVER_ENABLED=1

EOF

  

# start the service

systemctl start coturn

  

#get the ip of the current ec2 instace

PUBLIC_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)

INTERNAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)

DOMAIN=${domain}

FQDN=${subdomain}.${domain}

EMAIL=${email}

COTURN_USER=${username}

COTURN_PASS=${password}

# Don't forget any DNS related credentials
  

USER_INFO=$COTURN_USER:$COTURN_PASS

# or for an encrypted pw: 
# USER_INFO=$(turnadmin -k -u "$COTURN_USER" -r "$DOMAIN" -p "$COTURN_PASS" | head -n 1)

  

# set the config

cat >> /etc/turnserver.conf << EOF

realm=$DOMAIN

server-name=$FQDN

external-ip=$PUBLIC_IP/$INTERNAL_IP

cert=/etc/coturn/certs/$FQDN.cert

pkey=/etc/coturn/certs/$FQDN.key

user=$USER_INFO

lt-cred-mech

fingerprint

EOF

  

echo 'Creating DNS record...'

# update the dns records at your DNS provider with the public ip


# wait so that we can ensure the dns record is created and propogated
# might not be necessary but seemed safer than not waiting

sleep 30

  

echo 'Creating cert...'

# set the cert

certbot certonly -n --agree-tos --standalone --preferred-challenges http \

--deploy-hook "systemctl restart coturn" \

-d "$FQDN" \

-m "$EMAIL"

  

# was facing an issue like this without copying the certs to a dir
# that coturn could read:
# https://github.com/coturn/coturn/issues/1139

mkdir -p /etc/coturn/certs

cp "/etc/letsencrypt/live/$FQDN/cert.pem" "/etc/coturn/certs/$FQDN.cert"

cp "/etc/letsencrypt/live/$FQDN/privkey.pem" "/etc/coturn/certs/$FQDN.key"

chown turnserver -R "/etc/coturn/certs"

chmod 700 -R "/etc/coturn/certs"

  

service coturn restart

  

echo 'Done!'

The Terraform

And the terraform: TLDR: Spins up a keypair to be able to ssh into our ec2 instance, and the proper iam roles and permissions to do things in the least permissive way. A security group is also created with the proper ports open for our coturn server to talk to the rest of the web.


terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region = var.aws_region
}

locals {
  user_data = templatefile("${path.module}/init.tftpl", {
    subdomain        = var.subdomain
    domain           = var.domain
    username         = var.coturn_user
    password         = var.coturn_pass
    email            = var.email
    # any extra vars needed for DNS
  })
}

# key pair used to ssh in to the ec2 instance
resource "aws_key_pair" "deployer" {
  key_name   = "coturn_keypair"
  public_key = file(var.public_key)
}

resource "aws_instance" "app_server" {
  instance_type = "t3.nano"
  # ubuntu 22.10
  ami                  = "ami-0fc5d935ebf8bc3bc"
  key_name             = aws_key_pair.deployer.key_name
  iam_instance_profile = aws_iam_instance_profile.coturn.name
  security_groups      = [aws_security_group.coturn_sg.name]
  root_block_device {
    delete_on_termination = true
  }
  # init script
  user_data = local.user_data
  tags = {
    Name = "Coturn"
  }
}

# IAM roles and policy docs

# IAM role for the instance profile
resource "aws_iam_role" "role" {
  name               = "coturn_iam_role_ec2"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

# attach role to an instance profile for use with the ec2 instance
resource "aws_iam_instance_profile" "coturn" {
  name = "coturn_iam_profile_ec2"
  role = aws_iam_role.role.name
  tags = {
    Name = "CoturnAdmin"
  }
}

# ec2 role
data "aws_iam_policy_document" "assume_role" {
  statement {
    sid    = 1
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

# security group to open ports
resource "aws_security_group" "coturn_sg" {
  name        = "coturn_sg"
  description = "security group for the coturn ec2 instance"

  ingress {
    from_port   = 3478
    to_port     = 3479
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 3478
    to_port     = 3479
    protocol    = "udp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # tls
  ingress {
    from_port   = 5349
    to_port     = 5350
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

And the tf vars file:


variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "public_key" {
  description = "path to AWS keypair public key"
  type        = string
  default     = "~/.ssh/aws_keypair.pub"
}

variable "domain" {
  description = "the domain name of the server"
  type        = string
}

variable "subdomain" {
  description = "the subdomain of the server"
  type        = string
}

variable "username" {
  description = "the username for the lts creds"
  type        = string
}

variable "password" {
  description = "the pass for the lts creds"
  type        = string
}
variable "email" {
  description = "the email address for the ssl cert"
  type        = string
}

With these combined, you should be able to deploy a coturn server with the flick of the terraform apply wrist!

If you have a new A record on your DNS provider, that means at least that step worked.

If you need to SSH into the instance, make sure to open up SSH port 22 in the security group for your IP. You can do this in the terraform if you want, but I wouldn’t expect to need to get in to the server much except to grab the encrypted password. Also the AWS console offers a nice “My IP” option. I know it’s possible to code this into the terraform but it seemed like too much effort for not a lot gained.

Test the server with https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

Resources

These resources were extremely helpful in learning and implementing all of this WebRTC stuff:

https://gabrieltanner.org/blog/turn-server https://web.dev/articles/webrtc-basics https://web.dev/articles/webrtc-infrastructure https://codelabs.developers.google.com/codelabs/webrtc-web/#0 https://www.baeldung.com/webrtc https://webrtc.github.io/samples/ https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

And for actually implementing WebRTC in an application, specifically SvelteKit, these were helpful. I ended up using Socket.io instead of ws as implemented in the example:

https://github.com/suhaildawood/SvelteKit-integrated-WebSocket

This was helpful too as I had never worked with websockets before: https://joyofcode.xyz/using-websockets-with-sveltekit

Created on:
Last updated:
<< Go Back